﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace CodeAnalysisRuleCollection.Common.CSharp
{
    [ExportCodeRefactoringProvider("PromoteLocalToField", LanguageNames.CSharp)]
    internal class PromoteLocalToField : ICodeRefactoringProvider
    {
        public async Task<IEnumerable<CodeAction>> GetRefactoringsAsync(Document document, TextSpan span, CancellationToken cancellationToken)
        {
            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            if ((var constant = root.FindToken(span.Start).Parent.FirstAncestorOrSelf<VariableDeclaratorSyntax>()) != null)
            if ((var variable = constant.FirstAncestorOrSelf<LocalDeclarationStatementSyntax>()) != null)
            if (!variable.IsConst)
            {
                IList<CodeAction> actions = new List<CodeAction>();

                actions.Add(CodeAction.Create("Promote local to field", c => Fix(root, document, variable, constant, cancellationToken)));
                actions.Add(CodeAction.Create("Promote local to field with initialization", c => FixWithInitialize(root, document, variable, constant, cancellationToken)));

                //if (variable.Declaration.Variables.Count > 1)
                //    actions.Add(CodeAction.Create("Promote locals in declaration to fields", c => Fix(root, document, variable, cancellationToken)));

                return actions;
            }

            return null;
        }

        private async Task<Document> Fix(SyntaxNode root, Document document, LocalDeclarationStatementSyntax variable, VariableDeclaratorSyntax constant, CancellationToken cancellationToken)
        {
            TypeDeclarationSyntax type = variable.FirstAncestorOrSelf<TypeDeclarationSyntax>();

            // resolve type
            var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var fieldType = model.GetTypeInfo(variable.Declaration.Type).Type;

            // check static
            bool isStatic = false;
            if ((var method = variable.FirstAncestorOrSelf<BaseMethodDeclarationSyntax>(s => s .Modifiers.Any(SyntaxKind.StaticKeyword))) != null)
                isStatic = true;

            // remove declaration from method body
            var newType = type.ReplaceNode<SyntaxNode, StatementSyntax>(variable,
                SyntaxFactory.ExpressionStatement(
                    SyntaxFactory.BinaryExpression(SyntaxKind.SimpleAssignmentExpression,
                        SyntaxFactory.IdentifierName(constant.Identifier.ValueText),
                        constant.Initializer.Value
                    )
                )
                    .WithAdditionalAnnotations(Formatter.Annotation)
            );

            // add to type
            newType = CodeGenerator.AddFieldDeclaration(
                newType,
                CodeGenerationSymbolFactory.CreateFieldSymbol(
                    null,
                    Accessibility.Private,
                    new SymbolModifiers(isStatic: isStatic),
                    fieldType,
                    constant.Identifier.ValueText
                ),
                document.Project.Solution.Workspace
            );

            // replace root in document
            var newRoot = root.ReplaceNode(type, newType);

            return document.WithSyntaxRoot(newRoot);
        }

        private async Task<Document> FixWithInitialize(SyntaxNode root, Document document, LocalDeclarationStatementSyntax variable, VariableDeclaratorSyntax constant, CancellationToken cancellationToken)
        {
            TypeDeclarationSyntax type = variable.FirstAncestorOrSelf<TypeDeclarationSyntax>();

            // resolve type
            var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var fieldType = model.GetTypeInfo(variable.Declaration.Type).Type;

            // check static
            bool isStatic = false;
            if ((var method = variable.FirstAncestorOrSelf<BaseMethodDeclarationSyntax>(s => s.Modifiers.Any(SyntaxKind.StaticKeyword))) != null)
                isStatic = true;

            // remove declaration from method body
            var newType = type.RemoveNode(variable, default(SyntaxRemoveOptions));

            // add to type
            newType = CodeGenerator.AddFieldDeclaration(
                newType,
                CodeGenerationSymbolFactory.CreateFieldSymbol(
                    null,
                    Accessibility.Private,
                    new SymbolModifiers(isStatic: isStatic),
                    fieldType,
                    constant.Identifier.ValueText,
                    initializer: constant.Initializer.Value
                ),
                document.Project.Solution.Workspace
            );

            // replace root in document
            var newRoot = root.ReplaceNode(type, newType);

            return document.WithSyntaxRoot(newRoot);
        }

        private async Task<Document> Fix(SyntaxNode root, Document document, LocalDeclarationStatementSyntax variable, CancellationToken cancellationToken)
        {
            TypeDeclarationSyntax type = variable.FirstAncestorOrSelf<TypeDeclarationSyntax>();

            // resolve type
            var model = await document.GetSemanticModelAsync(cancellationToken);
            var fieldType = model.GetTypeInfo(variable.Declaration.Type).Type;

            // remove declaration from method body
            var newType = type.RemoveNode(variable, default(SyntaxRemoveOptions));

            // add to type
            foreach (var @var in variable.Declaration.Variables) // TODO: Concat declarations into a single statement
            {
                newType = CodeGenerator.AddFieldDeclaration(
                    newType,
                    CodeGenerationSymbolFactory.CreateFieldSymbol(
                        null,
                        Accessibility.Private,
                        default(SymbolModifiers),
                        fieldType,
                        @var.Identifier.ValueText,
                        initializer: @var.Initializer.Value
                    ),
                    document.Project.Solution.Workspace
                );
            }

            // replace root in document
            var newRoot = root.ReplaceNode(type, newType);

            return document.WithSyntaxRoot(newRoot);
        }
    }
}
